using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using BT.Sdk.CallFlowProvisioningCapability;
using BT.Sdk.InboundCallFlowCapability;
using BT.Sdk.OutboundCallFlowCapability;

namespace CallFlowDesigner
{
    enum BackgroundEvents
    {
        CallFlowStart,
        CallFlowFinished,
        CallFlowUpdated,
        DeleteFileStart,
        ListProvisionedFilesStart,
        ListProvisionedFilesStop,
        ListProvisionedFilesError,
        ListInboundMapsStart,
        ListInboundMapsStop,
        ListInboundMapsError,
        UploadFileStart,
        UploadFileStop,
        Error,
        BackgroundWorkerIdle,
        UploadMapStart,
        UploadMapStop,
        GetInformation,
        InformationRetrieved,
        SynchronizeFileCompleted,
        SynchronizeFoldersCompleted
    }
   
    public partial class CallFlowDesigner : Form
    { 
        bool dirty=false;
        private CallFlowProvisioningManager callFlowProvisioning = new CallFlowProvisioningManager();
        private OutboundCallFlowManager callFlowOutbound = new OutboundCallFlowManager();
        private InboundCallFlowManager callFlowInbound = new InboundCallFlowManager();
        private Information callFlowInformation = new Information();
        private ContextMenu rightClickMenu = new ContextMenu();
        private Point mouseDownPoint;
        private static string informationFromThread = string.Empty;

        private int progressbarMax = 0;
        private delegate void ChangeProgressBarCallback();

        #region vars

        string cachePath = Properties.Settings.Default.CachePath;
        string callid;
        string currentCert;
        //the form uses a BackgroundWorker to interface with the remote web service
        //this keeps the gui responsive, but does mean some cross thread operation
        //so there's a bunch of form level thread safe objects used for passing data across threads.
        string error = "";
        BackgroundWorker backgroundThread = new BackgroundWorker();
        DataTable flowTable = new DataTable();
        CallFlowValidator validator = new CallFlowValidator();
        InboundMap inboundMap = new InboundMap();
        NameValueCollection inboundMaps = new NameValueCollection();


        #region right click helper vars
        //these used for the right click insert. The actual values inserted are in a resx file
        Dictionary<string, string> snippets = new Dictionary<string, string>();
        const string Announcement = "Play an announcement";
        const string Dialog = "Connect the first caller to another phone";
        const string Call = "Start an Outbound Call";
        const string Prompt = "Play a prompt and collect touchtone input ";
        const string Hangup = "Disconnect all phones connected";
        const string DtmfGen = "Generate and send key presses to the connected call";
        const string Pause = "Pause for a number of seconds";
        const string MatchCallCaller = "Handle inbound calls based on the uri of the phone calling ";
        const string MatchCallCallee = "Handle inbound calls based on the the uri that's been called";
        const string Notification = "Send a notification to your web app";
        const string Subflow = "Collect and inject CallFlow elements from your web app";
        const string Record = "Record some audio and save it";
        const string vDtmfCollected = "Collected DTMF digits from a prompt element";
        const string vDtmfGenerated = "DTMF digits generated by the callflow";
        const string vCalledUri = "Uri of phone called by a call element";
        const string vAnnouncementAudio = "Audio file played by an announcement element";
        const string vInboundCallerNumber = "Uri of number that has called an inbound callflow (not available in Outbound Callflows)";
        const string vInboundCalledNumber = "Uri of number that has been called and started an inbound callflow(not available in Outbound Callflows)";
        const string vTimeStamp = "Provides the current date and time in format yyyyMMdd_HHmmss (e.g. 20071214_111611) ";
        const string vCallFlowId = "The unique identifier for the callflow instance currently being executed";
        #endregion

        #endregion

        #region Form Events

        ListViewColumnSorter inboundMapsSorter = new ListViewColumnSorter();

        public CallFlowDesigner()
        {
            InitializeComponent();
            lstInboundMapfiles.ListViewItemSorter = inboundMapsSorter;
                 

            BuildRightClickMenu();
            MainMenuSetup();
        }

        #region right click context menu in xml textbox

        private void MainMenuSetup()
        {
            MainMenu mainMenu = new MainMenu();
            MenuItem fileMenu = mainMenu.MenuItems.Add("&File");
            fileMenu.MenuItems.Add("&New", newCallFlow_Click);
            fileMenu.MenuItems.Add("&Open", openCallFlow_Click);
            fileMenu.MenuItems.Add("Save", saveCallFlow_Click).Shortcut = Shortcut.CtrlS;
            fileMenu.MenuItems.Add("Save As", saveAsCallFlow_Click);
            MenuItem editMenu = mainMenu.MenuItems.Add("&Edit");
            editMenu.MenuItems.Add("&Pretty Print", btnPrettyPrint_Click);
            editMenu.MenuItems.Add("&Cache", selectFolder_Click);

            this.Menu = mainMenu;
        }

        private void BuildRightClickMenu()
        {
            rightClickMenu.MenuItems.Add(Announcement, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(Call, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(Dialog, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(Prompt, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(Record, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(Hangup, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(DtmfGen, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(Pause, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(MatchCallCaller, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(MatchCallCallee, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(Notification, rightClickMenu_Click);
            rightClickMenu.MenuItems.Add(Subflow, rightClickMenu_Click);
            MenuItem[] mi = new MenuItem[8];
            mi[0] = new MenuItem(vAnnouncementAudio, rightClickMenu_Click);
            mi[1] = new MenuItem(vCalledUri, rightClickMenu_Click);
            mi[2] = new MenuItem(vDtmfCollected, rightClickMenu_Click);
            mi[3] = new MenuItem(vDtmfGenerated, rightClickMenu_Click);
            mi[4] = new MenuItem(vInboundCalledNumber, rightClickMenu_Click);
            mi[5] = new MenuItem(vInboundCallerNumber, rightClickMenu_Click);
            mi[6] = new MenuItem(vTimeStamp, rightClickMenu_Click);
            mi[7] = new MenuItem(vCallFlowId, rightClickMenu_Click);


            rightClickMenu.MenuItems.Add("Variables", mi);
            callFlowXml.ContextMenu = rightClickMenu;

            snippets.Add(Announcement, Properties.Snippets.Announcement);
            snippets.Add(Call, Properties.Snippets.Call);
            snippets.Add(Dialog, Properties.Snippets.Dialog);
            snippets.Add(Prompt, Properties.Snippets.Prompt);
            snippets.Add(Hangup, Properties.Snippets.Hangup);
            snippets.Add(DtmfGen, Properties.Snippets.DtmfGen);
            snippets.Add(Pause, Properties.Snippets.Pause);
            snippets.Add(MatchCallCaller, Properties.Snippets.MatchCallCaller);
            snippets.Add(MatchCallCallee, Properties.Snippets.MatchCallCallee);
            snippets.Add(Notification, Properties.Snippets.Notification);
            snippets.Add(Record, Properties.Snippets.Record);
            snippets.Add(Subflow, Properties.Snippets.Subflow);
            snippets.Add(vAnnouncementAudio, Properties.Snippets.vAnnouncementAudio);
            snippets.Add(vCalledUri, Properties.Snippets.vCalledUri);
            snippets.Add(vDtmfCollected, Properties.Snippets.vDtmfCollected);
            snippets.Add(vDtmfGenerated, Properties.Snippets.vDtmfGenerated);
            snippets.Add(vInboundCallerNumber, Properties.Snippets.vInboundCallerNumber);
            snippets.Add(vInboundCalledNumber, Properties.Snippets.vInboundCalledNumber);
            snippets.Add(vTimeStamp , Properties.Snippets.vTimeStamp);
            snippets.Add(vCallFlowId, Properties.Snippets.vCallFlowId);
        }
        private void rightClickMenu_Click(object o, EventArgs e)
        {
            callFlowXml.Insert(snippets[((MenuItem)o).Text]);
        }

        #endregion

        //validate callflow

        private void callFlowXml_TextChanged(object sender, EventArgs e)
        {
            if (validator.Validate(callFlowXml.Text))
            {
                callFlowXml.BackColor = Color.FromArgb(210, 255, 200);
                startCallFlow.Visible = true;
					 lblReport.Text = "";
		      }
            else
            {
                callFlowXml.BackColor = Color.FromArgb(255, 202, 255);
                startCallFlow.Visible = false;
					 lblReport.Text = validator.validationError;
            }
            dirty = true;
           
        }



        private void newCallFlow_Click(object sender, EventArgs e)
        {
            callFlowXml.Init();
				
				startCallFlow.Visible = true;
                callFlowXml.HighlightSyntax();
        }

        private void help_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process p = new System.Diagnostics.Process();
            p.StartInfo.FileName = "http://web21c.bt.com/api_documentation/callflow/0.4/tag_reference.html";
            p.StartInfo.UseShellExecute = true;
            p.Start();
            

        }

        string curXml;
        private void startCallFlow_Click(object sender, EventArgs e)
        {

            showTts.Visible = true;
            ttsCheats.Visible = false;

            flowGrid.DataSource = null;
            flowGrid.Refresh();
            curXml = callFlowXml.Text;
            StartBackgroundThread(new DoWorkEventHandler(backgroundThread_PlayCallFlow));
        }

        private void stopCallflow_Click(object sender, EventArgs e)
        {
            if (callid == null || callid == "") return;
            this.Cursor = Cursors.WaitCursor;

            try { if (callid != "") callFlowOutbound.StopCallFlow(callid); }
            finally { }
            stopCallflow.Visible = false;
            callFlowXml.Enabled = true;
            startCallFlow.Visible = true;
            this.Cursor = Cursors.Default;
        }

        private void cmdUploadFile_Click(object sender, EventArgs e)
        {
            ofd.Filter = "wave files (*.wav)|*.wav|XML files (*.xml)|*.xml";
            ofd.FileName = "";
            ofd.ShowHelp = false;
            ofd.Multiselect = false;
            ofd.CheckFileExists = true;
            ofd.Multiselect = true;
            ofd.Title = "Please select an 8bit ulaw encoded wav file or an inbound callflow XML file";
            ofd.ShowDialog();

            StartBackgroundThread(new DoWorkEventHandler(backgroundThread_UploadFile));
        }

        private void selectFolder_Click(object sender, EventArgs e)
        {
            cachePath = Properties.Settings.Default.CachePath;
            fbd.ShowNewFolderButton = true;
            fbd.Description = "Please select a folder to cache callflow files in";
            fbd.ShowDialog();

            if (cachePath != "" && fbd.SelectedPath != cachePath)
            {
                DialogResult dr = MessageBox.Show("Do you want to copy all your files from " + cachePath + " to " + fbd.SelectedPath + "?",
                    "Changing cache folder", MessageBoxButtons.YesNoCancel);
                if (dr == DialogResult.Cancel) return;
																						  
                if (dr == DialogResult.Yes)
                {
                    foreach (string file in Directory.GetFiles(Properties.Settings.Default.CachePath))
                    {
                        System.IO.FileInfo fi = new System.IO.FileInfo(file);
                        fi.CopyTo(fbd.SelectedPath + "\\" + fi.Name);
                    }
                }
            }

            cachePath = fbd.SelectedPath;
            Properties.Settings.Default.CachePath = cachePath;
            Properties.Settings.Default.Save();
            lblCacheFolder.Text = "Cache folder is " + cachePath;
            CheckCacheSynchronized();
        }
        private void cmdTranscode_Click(object sender, EventArgs e)
        {
            TranscodeAudio ta = new TranscodeAudio();
            ta.cf = this;
            ta.ShowDialog();
            ta.Close();
        }

        private void lstProvisionedFiles_DoubleClick(object sender, EventArgs e)
        {
            DataRowView v = (DataRowView)lstProvisionedFiles.SelectedItem;
            if (v.Row[0].ToString().ToLower().EndsWith(".xml"))
            {
                if (File.Exists(cachePath + "\\" + v.Row[0].ToString()))
                {
                    StreamReader sr = new StreamReader(cachePath + "\\" + v.Row[0].ToString());
                    callFlowXml.Text = sr.ReadToEnd();
                    sr.Close();
                }
                else
                {
                    MessageBox.Show(v.Row[0].ToString() + " not in cache folder", "CallFlowDesigner", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }

            else
            {
                playAudio_Click(sender, e);
            }
        }

        private void CheckCacheSynchronized()
        {
            string[] localFiles = Directory.GetFiles(cachePath);
            Collection<BT.Sdk.CallFlowProvisioningCapability.FileInfo> remoteFiles = callFlowProvisioning.ListFiles();
            bool complete = true;
            foreach (BT.Sdk.CallFlowProvisioningCapability.FileInfo f in remoteFiles)
            {
                bool b = false;
                foreach (string s in localFiles)
                {
                    if (s.Substring(s.LastIndexOf('\\') + 1).ToLower() == CleanUnixName(f.Name.ToLower()))
                    {
                        b = true;
                        break;
                    }
                }
                if (!b)
                {
                    complete = false;
                    break;
                }
            }
            if (!complete)
            {
                if (MessageBox.Show("The local folder is not synchronized with the storage on CallFlow\r\nWould you like to download all missing files now?", "CallFlow", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    StartBackgroundThread(new DoWorkEventHandler(backgroundThread_SynchronizeFolders));
                }
            }
        }

        private void cmdDeleteFile_Click(object sender, EventArgs e)
        {
            string[] selected = new string[lstProvisionedFiles.SelectedItems.Count];

            for (int cnt = 0; cnt <= lstProvisionedFiles.SelectedItems.Count - 1; cnt++)
            {
                DataRowView row = (DataRowView)lstProvisionedFiles.SelectedItems[cnt];
                selected[cnt] = row[0].ToString();
            }

            StartBackgroundThread(new DoWorkEventHandler(backgroundThread_DeleteFile), selected);
        }

        private void openCallFlow_Click(object sender, EventArgs e)
        {
            ofd.Filter = "CallFlow xml|*.xml";
            ofd.InitialDirectory = Properties.Settings.Default.CachePath;
            ofd.Multiselect = false;
            ofd.CheckFileExists = true;
            ofd.FileName = "";

            if (ofd.ShowDialog() != DialogResult.Cancel)
            {
                sfd.FileName = ofd.FileName;
                StreamReader sr = new StreamReader(ofd.FileName);
                callFlowXml.Text = sr.ReadToEnd();
                sr.Close();
            }
        }

        private void saveAsCallFlow_Click(object sender, EventArgs e)
        {
            AskForFileNameAndSaveCallFlow();
            dirty = false;
        }

        private void saveCallFlow_Click(object sender, EventArgs e)
        {
            if (!File.Exists(sfd.FileName))
            {
                AskForFileNameAndSaveCallFlow();
                dirty = false;
            }
            else
            {
                SaveCallFlowToSFDFileName();
                dirty = false;
            }
        }

        private void uploadInboundMap_Click(object sender, EventArgs e)
        {
            inboundMap.cf = this;
            inboundMap.ShowDialog();

            if (inboundMap.MapResult == InboundMap.Result.Upload)
            {
                StartBackgroundThread(new DoWorkEventHandler(backgroundThread_SetInboundMap));
            }
        }

        private void deleteInboundMap_Click(object sender, EventArgs e)
        {
            if (lstInboundMapfiles.SelectedItems.Count == 0) return;
            if  (lstInboundMapfiles.SelectedItems[0].Text.StartsWith("unbound"))
            {
                MessageBox.Show("Can't delete an unbound mapping","CallFlow",MessageBoxButtons.OK,MessageBoxIcon.Exclamation);
                return;
            }
            StartBackgroundThread(new DoWorkEventHandler(backgroundThread_DeleteInboundMap), lstInboundMapfiles.SelectedItems[0].SubItems[1].Text);
        }

        private void playAudio_Click(object sender, EventArgs e)
        {
            if (lstProvisionedFiles.SelectedItems.Count ==0) return;
            DataRowView i = (DataRowView)lstProvisionedFiles.SelectedItems[0];
            string f = Properties.Settings.Default.CachePath + "\\" + CleanUnixName(i.Row[0].ToString());
            if (File.Exists(f))
            {
                if ((new System.IO.FileInfo(f)).Extension.ToLower() == ".wav")
                {
                    //Stop has not been implemented.
                    WAVSounds.Play(f, PlaySoundFlags.SND_ASYNC);
                }
                else
                {
                    MessageBox.Show("Don't try to play an XML file!", "CallFlow", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
            }
            else
            {
                MessageBox.Show(lstProvisionedFiles.SelectedValue + " not in cache folder ", "CallFlow", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void btnPrettyPrint_Click(object sender, EventArgs e)
        {
            try
            {
                using (StringWriter stringWriter = new StringWriter())
                {
                    XmlDocument doc = new XmlDocument();

                    //get your document

                    doc.LoadXml(callFlowXml.Text);
						 //create reader and writer

                    XmlNodeReader xmlReader = new XmlNodeReader(doc);
						  XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);

                    //set formatting options
						  xmlWriter.Formatting = Formatting.Indented;
						  xmlWriter.Indentation = 1;
  						  xmlWriter.IndentChar = '\t';

                    //write the document formatted
                    xmlWriter.WriteNode(xmlReader, true);
                    callFlowXml.Text = stringWriter.ToString();

                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void btnGetInfo_Click(object sender, EventArgs e)
        {
            callFlowInformation.CallFlowDesigner = this;
            callFlowInformation.ShowDialog();

            if (callFlowInformation.GetInformationFlag == Information.Result.GetInformation)
            {
                StartBackgroundThread(new DoWorkEventHandler(backgroundThread_GetInformation));
            }
        }

        private void btnRefresh_Click(object sender, EventArgs e)
        {
            string temp = callFlowXml.Text;
            CallFlowDesigner_Load(this, null);
            callFlowXml.Text = temp;
            btnPrettyPrint_Click(this, null);
        }

        private void CallFlowDesigner_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Control && e.KeyCode == Keys.S)
            {
                saveCallFlow_Click(sender, e);
                e.Handled = true;
            }
        }

        #endregion

        #region Starting up

        private void CallFlowDesigner_Load(object sender, EventArgs e)
        {
			 
			   currentCert = ConfigurationManager.AppSettings["certFile"];
			 
			  //load ttscheatsheet
            Assembly a = Assembly.GetExecutingAssembly();
            Stream fs = a.GetManifestResourceStream(a.ManifestModule.Name.Replace(".exe", ".Resources.TtsCheats.rtf"));
            txtTts.LoadFile(fs,RichTextBoxStreamType.RichText);
            fs.Close();


				if (Properties.Settings.Default.Font == null)
				{
					Properties.Settings.Default.Font = callFlowXml.Font;
					Properties.Settings.Default.Save();
				}
				callFlowXml.Font = Properties.Settings.Default.Font;
                callFlowXml.HighlightSyntax();

            CheckCacheFolder();
				
            sfd.InitialDirectory = Properties.Settings.Default.CachePath;
            ofd.InitialDirectory = Properties.Settings.Default.CachePath;
            fbd.SelectedPath = Properties.Settings.Default.CachePath;
				StartBackgroundThread(new DoWorkEventHandler(backgroundThread_StartUp));
            
			   dirty = false;
        }

        

        private void CheckCacheFolder()
        {
            if (!Directory.Exists(Properties.Settings.Default.CachePath))
            {
                Properties.Settings.Default.CachePath = "";
                Properties.Settings.Default.Save();
            }

            lblCacheFolder.Text = Properties.Settings.Default.CachePath;
            if (lblCacheFolder.Text == "")
            {
                selectFolder_Click(this, null);
            }
        }

        //effectively start a dir *.* against callflow provisioning service
        void backgroundThread_StartUp(object sender, DoWorkEventArgs e)
        {
            UpdateGui(BackgroundEvents.ListProvisionedFilesStart);

            GetProvisionedFiles();
           
            UpdateGui(BackgroundEvents.ListInboundMapsStart);

            GetInboundMappings();

            if (error == "") { UpdateGui(BackgroundEvents.ListInboundMapsStop); } else { return; }
            if (error == "") { UpdateGui(BackgroundEvents.BackgroundWorkerIdle); } else { return; }
        }

        #endregion

        //mostly run in background thread, and have no access to gui
        #region callflow interactions

        private void GetInboundMappings()
        {
            try
            {
                Collection<InboundCallMapping> maps = callFlowInbound.ListInboundCallMappings();
                
                inboundMaps.Clear();

                int inbound = 0;
                foreach (InboundCallMapping m in maps)
                {
                    if (m.Name =="")
                    {
                        m.Name = "unbound" + inbound;
                        inbound++;
                    }
                    inboundMaps.Add(m.Name, m.DestinationUri);
                    
                }
                UpdateGui(BackgroundEvents.ListInboundMapsStop);
            }
            catch (Exception ex)
            {
                error = ex.Message;
                UpdateGui(BackgroundEvents.Error);
            }
        }

        private void GetProvisionedFiles()
        {
            try
            {
                Collection<BT.Sdk.CallFlowProvisioningCapability.FileInfo> files = callFlowProvisioning.ListFiles();
                DataTable dt = new DataTable();
                dt.Columns.Add("Filename");
                foreach (BT.Sdk.CallFlowProvisioningCapability.FileInfo f in files)
                {
                    dt.Rows.Add(f.Name);
                }
                lstProvisionedFiles.DataSource = dt;
                UpdateGui(BackgroundEvents.ListProvisionedFilesStop);
            }
            catch (Exception ex)
            {
                error = ex.Message;
                UpdateGui(BackgroundEvents.Error);
            }
        }

        private void changeProgressBar()
        {
            if (progressBar_FileUpload.InvokeRequired)
            {
                // instantiate the callback instance out of this very method
                ChangeProgressBarCallback callback = new ChangeProgressBarCallback(changeProgressBar);

                // invoke it, when it comes to it again InvokeRequired will be false
                Invoke(callback);
            }

            else
            {
                progressBar_FileUpload.Maximum = progressbarMax;
                progressBar_FileUpload.Step = 1;

                if (progressBar_FileUpload.Value == progressBar_FileUpload.Maximum)
                {
                    progressBar_FileUpload.Value = 0;
                    label_status.Text = "0/0";
                }

                else
                {
                    progressBar_FileUpload.Value++;
                    label_status.Text = progressBar_FileUpload.Value + "/" + progressBar_FileUpload.Maximum;
                }
            }
        }

        public void backgroundThread_UploadFile(object sender, DoWorkEventArgs e)
        {
            string[] selectedFiles = ofd.FileNames;
            progressbarMax = (ofd.FileNames.Length);

            try
            {
                UpdateGui(BackgroundEvents.UploadFileStart);

                foreach (string filename in selectedFiles)
                {
                    FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);

                    Byte[] file = new Byte[fs.Length];
                    fs.Read(file, 0, Convert.ToInt32(fs.Length)); //read it into the byte array.
                    fs.Close();

                    //strip off all directory structure
                    string strippedFilename = filename.Substring(filename.LastIndexOf('\\')).Split('\\')[1];

                    //upload
                    callFlowProvisioning.PutFile(strippedFilename, file, true);

                    //copy to cache
                    if (filename != cachePath + "\\" + strippedFilename)
                    {
                        File.Copy(filename, cachePath + "\\" + strippedFilename, true);
                    }

                    UpdateGui(BackgroundEvents.UploadFileStop);

                    //update list of files
                    UpdateGui(BackgroundEvents.ListProvisionedFilesStart);

                    //refresh list of files on callflow service
                    GetProvisionedFiles();

                    UpdateGui(BackgroundEvents.BackgroundWorkerIdle);
                    changeProgressBar();
                }

                Thread.Sleep(2500);
                changeProgressBar();
            }
            catch (Exception ex)
            {
                error = ex.Message;
                UpdateGui(BackgroundEvents.Error);
            }
        }

        void backgroundThread_DeleteFile(object sender, DoWorkEventArgs e)
        {
            string[] selected = (string[]) e.Argument;
            UpdateGui(BackgroundEvents.DeleteFileStart);
            progressbarMax = selected.Length;

            foreach (string filename in selected)
            {
                UpdateGui(BackgroundEvents.ListProvisionedFilesStart);

                callFlowProvisioning.DeleteFile(filename);
                GetProvisionedFiles();

                UpdateGui(BackgroundEvents.BackgroundWorkerIdle);
                changeProgressBar();
            }

            changeProgressBar();
        }

        void backgroundThread_PlayCallFlow(object sender, DoWorkEventArgs e)
        {
            UpdateGui(BackgroundEvents.CallFlowStart);

            //start the callflow
            try
            {
                callid = callFlowOutbound.StartCallFlow(curXml);
            }
            catch (Exception ex)
            {
                error = ex.Message;
                UpdateGui(BackgroundEvents.CallFlowFinished);
                UpdateGui(BackgroundEvents.Error);
                return;
            }

            //monitor the callflow
            while (true)
            {
                System.Threading.Thread.Sleep(100);

                //get the XML report of the flow to date.
                string progress = callFlowOutbound.GetCallFlowInformation(callid);

                Debug.WriteLine(progress);

                //start building a datatable of findings
                flowTable = new DataTable();
                flowTable.Columns.Add("Id");
                flowTable.Columns.Add("StartTime");
                flowTable.Columns.Add("StopTime");
                flowTable.Columns.Add("Data");
                //load xml into reader

					 bool callFlowFinished = false;
					 string pendingOperation = "";

					using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(progress)))
					 {
						 using (XmlTextReader xtr = new XmlTextReader(ms))
						 {

							 while (xtr.Read())
							 {
								 if (xtr.NodeType == XmlNodeType.Element)
								 {
									 if (xtr.Name == "flowexecution") // this is global start/end state, and is the only special case
									 {

										 flowTable.Rows.Add("Callflow", GetTimeFromXml(xtr["startTime"]), null);
										 pendingOperation = xtr["pendingElementID"];

										 if (xtr["status"] == "finished" || xtr["status"] == "failed")
										 {
											 flowTable.Rows[0]["StopTime"] = GetTimeFromXml(xtr["endTime"]);
											 flowTable.Rows[0]["Data"] = xtr["errorMessage"];
											 callFlowFinished = true;
											 if (xtr["status"] == "failed")
											 {
												 MessageBox.Show("CallFlow failed.\r\n" + xtr["errorMessage"], "CallFlowDesigner", MessageBoxButtons.OK, MessageBoxIcon.Error);
											 }

										 }
									 }
									 else if (xtr["id"] != null && xtr["id"] != "")
									 {
										 flowTable.Rows.Add(xtr["id"], GetTimeFromXml(xtr["startTime"]), GetTimeFromXml(xtr["endTime"]), xtr["responseLine"]);
									 }

								 }
							 }
						 }
						 ms.Close();
					 }
                if (pendingOperation != "") flowTable.Rows.Add(pendingOperation, "", "");

                UpdateGui(BackgroundEvents.CallFlowUpdated);

                if (callFlowFinished) break;
            }

            UpdateGui(BackgroundEvents.CallFlowFinished);
            UpdateGui(BackgroundEvents.BackgroundWorkerIdle);
        }

        private string GetTimeFromXml(string xmlDate)
        {
            return (xmlDate == null) ? "" : xmlDate.Split('T')[1].Split('+')[0].Replace("Z", "");
        }

        void backgroundThread_SetInboundMap(object sender, DoWorkEventArgs e)
        {

            UpdateGui(BackgroundEvents.UploadMapStart);
            try
            {
                callFlowInbound.AddInboundCallMapping(inboundMap.SipAddress, inboundMap.FileName);

                UpdateGui(BackgroundEvents.ListInboundMapsStart);

                GetInboundMappings();

                UpdateGui(BackgroundEvents.ListInboundMapsStop);
                UpdateGui(BackgroundEvents.BackgroundWorkerIdle);
            }
            catch (Exception ex)
            {
                this.error = ex.Message;
                UpdateGui(BackgroundEvents.Error);
            }

        }

        void backgroundThread_DeleteInboundMap(object sender, DoWorkEventArgs e)
        {
            UpdateGui(BackgroundEvents.DeleteFileStart);

            string arg = (string)e.Argument;
            string[] args = arg.Split(',');
            foreach (string s in args)
            {
                callFlowInbound.RemoveInboundCallMapping(s);
            }
            UpdateGui(BackgroundEvents.ListInboundMapsStart);

            GetInboundMappings();

            UpdateGui(BackgroundEvents.ListInboundMapsStop);
            UpdateGui(BackgroundEvents.BackgroundWorkerIdle);

        }

        void backgroundThread_GetInformation(object sender, DoWorkEventArgs e)
        {
            UpdateGui(BackgroundEvents.GetInformation);
            int timeSlip = callFlowInformation.TimeSpan;
            string infos = string.Empty;
				Collection<string> uriS = new Collection<string>();
						
			  foreach (string uri in callFlowInformation.SelectedUris)
				{
					
					uriS.Add(uri);
				}

				try
				{
					infos += callFlowInbound.GetCallFlowInformations(uriS, timeSlip);
				}
				catch { }
				

            informationFromThread = infos.Replace("&quot;","'");
				if (informationFromThread != "")
				{
					flowTable = new DataTable();
					flowTable.Columns.Add("Id");
					flowTable.Columns.Add("StartTime");
					flowTable.Columns.Add("StopTime");
					flowTable.Columns.Add("Data");
					//load xml into reader
					using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(informationFromThread)))
					{
						using (XmlTextReader xtr = new XmlTextReader(ms))
						{
							while (xtr.Read())
							{
								if (xtr.NodeType == XmlNodeType.Element)
								{
									if (xtr.Name == "flowexecution") // this is global start/end state, and is the only special case
									{
									  string data = String.Format("Source {0}, Dest {1}, flow {2}",xtr["sourceUri"],xtr["destinationUri"],xtr["flowName"]);
										flowTable.Rows.Add("Callflow " + xtr["status"], GetTimeFromXml(xtr["startTime"]),GetTimeFromXml(xtr["endTime"]), data);
			
									}
									else if (xtr["id"] != null && xtr["id"] != "")
									{
										string data = "";
										if (xtr.Name == "prompt") data = xtr["input"];
										if (xtr.Name == "notification") data = xtr["uri"];
										if (xtr.Name == "announcement") data = xtr["audio"];
										flowTable.Rows.Add(xtr["id"], GetTimeFromXml(xtr["startTime"]), GetTimeFromXml(xtr["endTime"]), data);
									}

								}
							}
						}
						ms.Close();
					}
				}
			  
			  UpdateGui(BackgroundEvents.InformationRetrieved);
			  if (infos.Length > 0) UpdateGui(BackgroundEvents.BackgroundWorkerIdle);
        }

        string lastSync = "";
        void backgroundThread_SynchronizeFolders(object sender, DoWorkEventArgs e)
        {
            string[] localFiles = Directory.GetFiles(cachePath);
            Collection<BT.Sdk.CallFlowProvisioningCapability.FileInfo> remoteFiles = callFlowProvisioning.ListFiles();
            foreach (BT.Sdk.CallFlowProvisioningCapability.FileInfo f in remoteFiles)
            {
                bool local = false;
                foreach (string s in localFiles)
                {
                    if (CleanUnixName(f.Name.ToLower()) == s.Substring(s.LastIndexOf('\\')+1).ToLower())
                    {
                        local = true;
                        break;
                    }
                }
                if (!local)
                {
                    byte[] file = callFlowProvisioning.GetFile(f.Name);
                    File.WriteAllBytes(cachePath + "\\" + CleanUnixName(f.Name), file);
                    lastSync = f.Name;
                    UpdateGui(BackgroundEvents.SynchronizeFileCompleted);

                }
            }
            UpdateGui(BackgroundEvents.SynchronizeFoldersCompleted);

        }

        private string CleanUnixName(string s)
        {
            return System.Text.RegularExpressions.Regex.Replace(s, "[(^\\/:\"*?<>|)]+", ""); 
        }

        #endregion

        #region BackgroundWorker stuff

        //helper argument to kick off the background threads.
        public void StartBackgroundThread(DoWorkEventHandler eventHandler)
        {
            StartBackgroundThread(eventHandler, null);
        }
        private void StartBackgroundThread(DoWorkEventHandler eventHandler, object argument)
        {
            backgroundThread = new BackgroundWorker();
            backgroundThread.WorkerReportsProgress = true;
            backgroundThread.DoWork += eventHandler;
            backgroundThread.ProgressChanged += new ProgressChangedEventHandler(backgroundThread_ProgressChanged);
            backgroundThread.RunWorkerAsync(argument);
        }

        //run on background thread, moves context straight to backgroundThread_ProgressChanged on gui thread
        private void UpdateGui(BackgroundEvents status)
        {
            backgroundThread.ReportProgress((int)status);
        }

        //runs on form thread. Used mostly for updating the form in response to callflow interactions
        void backgroundThread_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            switch ((BackgroundEvents)e.ProgressPercentage)
            {
                case BackgroundEvents.CallFlowStart:
                    startCallFlow.Visible = false;
                    stopCallflow.Visible = true;
                    callFlowXml.Enabled = false;
                    lblReport.Text = "Call Flow Running";
                    break;

                case BackgroundEvents.CallFlowFinished:
                    startCallFlow.Visible = true;
                    stopCallflow.Visible = false;
                    callFlowXml.Enabled = true;
                    break;

                case BackgroundEvents.CallFlowUpdated:
                    flowGrid.DataSource = flowTable;
                    flowGrid.Columns[3].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
                    flowGrid.Refresh();
                    
                    break;


                case BackgroundEvents.ListProvisionedFilesStart:
                    lblReport.Text = "Listing files...";
                    break;

                //updates the listbox. The datasource can be changed on the background thread
                // but the ui can't be refreshed. some moving around of the data seems to be required.
                case BackgroundEvents.ListProvisionedFilesStop:
                    object p = lstProvisionedFiles.DataSource;
                    lstProvisionedFiles.DataSource = null;
                    lstProvisionedFiles.Refresh();
                    lstProvisionedFiles.DataSource = p;
                    lstProvisionedFiles.DisplayMember = "Filename";
                    lstProvisionedFiles.ValueMember = "Filename";
                    lstProvisionedFiles.Refresh();
                    CheckCacheSynchronized();
                    break;

                case BackgroundEvents.DeleteFileStart:
                    lblReport.Text = "Deleting";
                    break;

                case BackgroundEvents.UploadMapStart:
                    lblReport.Text = "Uploading Inbound Map";
                    break;


                case BackgroundEvents.ListInboundMapsStart:
                    lblReport.Text = "Listing inbound mappings...";
                    break;

                //updates the multicolumn listview, by using a namevaluecollection across threads
                case BackgroundEvents.ListInboundMapsStop:

                    lstInboundMapfiles.Items.Clear();

                    foreach (string s in inboundMaps.AllKeys)
                    {
                        string[] t = new string[2];
                        t[0] = s;
                        t[1] = inboundMaps[s];
                        ListViewItem i = new ListViewItem(t);
                        lstInboundMapfiles.Items.Add(i);
                    }
                    lstInboundMapfiles.Columns[1].Width = -2;
                    lstInboundMapfiles.Sort();
                    break;

                case BackgroundEvents.UploadFileStart:
                    lblReport.Text = "Uploading  file...";
                    break;

                case BackgroundEvents.Error:
                    lblReport.Text = "Error - " + error;
                    break;

                case BackgroundEvents.BackgroundWorkerIdle:
                    lblReport.Text = currentCert;
                    break;

                case BackgroundEvents.GetInformation:
                    lblReport.Text = "Retrieving inbound call information";
                    break;

                case BackgroundEvents.InformationRetrieved:
						  if (informationFromThread != "")
						  {
							  flowGrid.DataSource = flowTable;
							  flowGrid.Columns[3].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
							  flowGrid.Refresh();
                    
							  callFlowXml.Text = informationFromThread;
							  btnPrettyPrint_Click(sender, e);
						  }
						  else
						  {
							  lblReport.Text = "No inbound calls match criteria";
						  }
						  break;

                case BackgroundEvents.SynchronizeFileCompleted:
                    lblReport.Text = "Retrieved " + lastSync +" from server";
                    break;
                case BackgroundEvents.SynchronizeFoldersCompleted:
                    lblReport.Text = "Retrieved all files from server";
                    break;

            }

        }

        #endregion

        private void AskForFileNameAndSaveCallFlow()
        {
            sfd.OverwritePrompt = true;
            sfd.Filter = "CallFlow xml|*.xml";

            if (sfd.ShowDialog() == DialogResult.OK)
            {
                SaveCallFlowToSFDFileName();
              
            }
            
        }

        private void SaveCallFlowToSFDFileName()
        {
            StreamWriter sw = new StreamWriter(sfd.FileName, false);
            sw.Write(callFlowXml.Text);
            sw.Close();
            dirty = false;
        }

        private void CallFlowDesigner_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (dirty == true)
            {
                DialogResult msgres = new DialogResult();

                msgres = MessageBox.Show("Do you want to save changes?", "File Save", MessageBoxButtons.YesNoCancel);
                if (msgres == DialogResult.Yes)
                {
                    //e.Cancel = true;
                    AskForFileNameAndSaveCallFlow();
                    dirty = false;
                }
                else if (msgres == DialogResult.Cancel)
                    e.Cancel = true;
            }
        }

        private void showTts_Click(object sender, EventArgs e)
        {
            ttsCheats.Visible = true;
            showTts.Visible = false;
        }

        private void hideTts_Click(object sender, EventArgs e)
        {
            showTts.Visible = true;
            ttsCheats.Visible = false;
        }

        private void lstInboundMapfiles_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Determine if clicked column is already the column that is being sorted.
            if (e.Column == inboundMapsSorter.SortColumn)
            {
                // Reverse the current sort direction for this column.
                if (inboundMapsSorter.Order == SortOrder.Ascending)
                {
                    inboundMapsSorter.Order = SortOrder.Descending;
                }
                else
                {
                    inboundMapsSorter.Order = SortOrder.Ascending;
                }
            }
            else
            {
                // Set the column number that is to be sorted; default to ascending.
                inboundMapsSorter.SortColumn = e.Column;
                inboundMapsSorter.Order = SortOrder.Ascending;
            }

            // Perform the sort with these new sort options.
            this.lstInboundMapfiles.Sort();

        }

		  private void txtTts_LinkClicked(object sender, LinkClickedEventArgs e)
		  {
			  ProcessStartInfo psi = new ProcessStartInfo(e.LinkText);
			  Process.Start(psi);	
		  }

		  private void txtfont_Click(object sender, EventArgs e)
		  {
			  font.Font = callFlowXml.Font;

			  if (font.ShowDialog() == DialogResult.OK)
			  {
				  callFlowXml.Font = font.Font;

				  Properties.Settings.Default.Font = font.Font;
				  Properties.Settings.Default.Save();
			  }
		  }



    }
}
